package com.zqs.compareface.utils.impl;

import com.zqs.compareface.controller.StreamController;
import com.zqs.compareface.server.local.FaceImageContrast;
import com.zqs.compareface.server.local.LocalDetectionFace;
import com.zqs.compareface.utils.ConnectionLocal;
import com.zqs.compareface.utils.ThreadPoolController;
import com.zqs.compareface.utils.impl.threadpool.TheCustomDynamicThreadPoolUtil;
import com.zqs.compareface.utils.impl.threadpool.myLinkedBlockingQueue.ReWritedCapacityLinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.VideoWriter;
import org.opencv.videoio.Videoio;

import java.nio.file.FileSystems;
import java.nio.file.WatchService;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

/**
 *
 * @Title: Opencv 图片人脸识别、实时摄像头人脸识别、视频文件人脸识别
 * @Description: OpenCV-4.5.3 测试文件
 * @date:
 * @version: V-1.0.0
 *
 */
public class LocalCheckUtil implements ConnectionLocal, ThreadPoolController {

    // 初始化人脸探测器
    static CascadeClassifier faceDetector;

    static int i=0;
    private static int count;
    private static int counts;
    private static Point lastPoint = new Point(0.0, 0.0);
    private static double [][] lastPointList = {{0,0},{0,0}};
    private static double [][] currentPointList = {{0,0},{0,0}};

    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        faceDetector = new CascadeClassifier(OPENCV_CLASS_PATH);
    }
    protected final Log log = LogFactory.getLog(getClass());
    public Mat faceimage;
    public List<Mat> locallist;
    public LocalCheckUtil(List<Mat> locallist) {
        this.locallist = locallist;
    }
    public LocalCheckUtil() {
    }

    public static void main(String[] args) {
        // 1- 从摄像头实时人脸识别，识别成功保存图片到本地
//        getVideoFromCamera();

        // 2- 从本地视频文件中识别人脸
//        getVideoFromFile();

        // 3- 本地图片人脸识别，识别成功并保存人脸图片到本地
//        findFace();

        // 4- 比对本地2张图的人脸相似度 （越接近1越相似）
//        faceByFace();
    }

    /**
     * 比对本地2张图的人脸相似度 （越接近1越相似）
     */
    public static void faceByFace(String basePicPathDir){
//        String basePicPath = "C:\\Users\\zouqi\\Pictures\\";
        String basePicPath = basePicPathDir;
        double compareHist = compare_image(basePicPath + "wu1.jpg", basePicPath + "face.png");
        System.out.println(compareHist);
        if (compareHist > 0.72) {
            System.out.println("人脸匹配");
        } else {
            System.out.println("人脸不匹配");
        }
    }

    /**
     * 人脸对比 1对多 传入一张人脸图片和全库对比
     * @param
     * @return
     */
    public static double compare_allimage(Mat cameraImg){
        List<Mat> list = GetEachDirImagesUtil.imageMatList;
        double compareHist = 0.0;
        for (Mat localImagPath:list) {
            compareHist = compare_image(cameraImg,localImagPath);
            //只要检测到了匹配的人脸即可终止后面的循环
            if (compareHist > 0.7) {
                return compareHist;
            }
            if (compareHist > 0.5 && compareHist <= 0.7) {
                return compareHist;
            }
            if (compareHist <= 0.5){
                return compareHist;
            }
        }
        return compareHist;
    }

    /**
     * OpenCV-4.5.3 从摄像头实时读取
     * @return: void
     * @date:
     */
    public static void getVideoFromCamera() {
        //1 如果要从摄像头获取视频 则要在 VideoCapture 的构造方法写 0
        VideoCapture capture=new VideoCapture(0);
        Mat video=new Mat();
        int index=0;
        if (capture.isOpened()) {
            while(i<1000) {// 匹配成功3次退出
                capture.read(video);
                HighGui.imshow("实时人脸识别", getFace(video));
                index=HighGui.waitKey(100);
                if (index==27) {
                    capture.release();
                    break;
                }
            }
        }else{
            System.out.println("摄像头未开启");
        }
        try {
            capture.release();
            Thread.sleep(1000);
            System.exit(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return;
    }

    /**
     * 通过视频串流来检测人脸并标注
     * @param
     * @param
     */
    public static void getVideoFromStream() {
        String streamURL = StreamController.STREAM_URL;
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamURL);
        grabber.setImageWidth(640);
        grabber.setImageHeight(480);
        grabber.setFrameRate(30.0);
        grabber.setVideoBitrate(40000);
        try {
            grabber.start();
        } catch (FFmpegFrameGrabber.Exception e) {
            e.printStackTrace();
        }
        //转换器
        OpenCVFrameConverter.ToOrgOpenCvCoreMat converter = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
        //开始抽帧和调用检测方法
        while (grabber != null) {
            try {
                Frame frame = grabber.grabImage();
                Mat mat = converter.convert(frame);
                LocalCheckUtil.getFace(mat);
            } catch (FFmpegFrameGrabber.Exception e) {
                e.printStackTrace();
            }
        }
        try {
            grabber.release();
            grabber.stop();
        } catch (FFmpegFrameGrabber.Exception e) {
            e.printStackTrace();
        }

    }
    /**
     * OpenCV-4.5.3 从视频文件中读取
     * @return: void
     * @date:
     */
    public static void getVideoFromFile() {
        VideoCapture capture=new VideoCapture();
        capture.open("c:\\avi.mp4");//1 读取视频文件的路径

        if(!capture.isOpened()){
            System.out.println("读取视频文件失败！");
            return;
        }
        Mat video=new Mat();
        int index=0;
        while(capture.isOpened()) {
            capture.read(video);//2 视频文件的视频写入 Mat video 中
            HighGui.imshow("本地视频识别人脸", getFace(video));//3 显示图像
            index=HighGui.waitKey(100);//4 获取键盘输入
            if(index==27) {//5 如果是 Esc 则退出
                capture.release();
                return;
            }
        }
    }

    /**
     * OpenCV-4.5.3人脸检测 获取视频流中的Mat图
     * @date:
     * @param image 待处理Mat图片(视频中的某一帧)
     * @return 处理后的图片
     */
    public static Mat getFace(Mat image) {
        // 1 读取OpenCV自带的人脸识别特征XML文件(faceDetector)
//        CascadeClassifier facebook=new CascadeClassifier("D:\\Sofeware\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
        // 2  特征匹配类
        MatOfRect face = new MatOfRect();
        // 3 特征匹配
        faceDetector.detectMultiScale(image, face);
        Rect[] rects=face.toArray();
        System.out.println("检测到 "+rects.length+" 个人脸");
        //如果检测到了人脸
        if(rects != null && rects.length >= 1) {
            System.out.println("检测到 "+rects.length+" 个人脸");
            // 4 为每张识别到的人脸画一个圈
           for (int i = 0; i < rects.length; i++) {
               Imgproc.rectangle(image, new Point(rects[i].x, rects[i].y), new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 255, 0));
//               Imgproc.putText(image, "Human", new Point(rects[i].x, rects[i].y), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
//               Mat dst=image.clone();
//               Imgproc.resize(image, image, new Size(300,300));
            }
            i++;
            if(i==2) {// 获取匹配成功第i次的照片
                Imgcodecs.imwrite(ConnectionLocal.STORE_FILE_PATH + "face.png", image);
            }
       }
        return image;
    }

    /**
     * 一对一，两个点之间的欧式距离计算
     * @param vector1
     * @param vector2
     */
    public static void sim_distance(double[] vector1 ,double[] vector2){
        double distance = 0;
        if (vector1.length == vector2.length) {
            for (int i = 0; i < vector1.length; i++) {
                double temp = Math.pow((vector1[i] - vector2[i]), 2.0);
                distance += temp;
            }
            distance = Math.sqrt(distance);
        }
        System.out.println("single欧氏距离"+distance);
    }

    /**
     * 向量a与矩阵的欧式距离
     * @param vector1
     * @param vector2
     */
    public static void jsim_distance(double[] vector1, double[][] vector2) {
        double distance[]= new double[vector2.length];
        if (vector1.length == vector2[0].length) {
            for (int i = 0; i < vector1.length; i++) {
                for(int j=0; j < vector2.length; j++){
                    try {
                        distance[j] += Math.pow((vector1[i] - vector2[j][i]), 2.0);
                    }catch (ArrayIndexOutOfBoundsException e){
                        e.printStackTrace();
                    }
                }  }}
        for (int i = 0; i < distance.length; i++) {
            distance[i] = Math.sqrt(distance[i]); }

        for(int i=0;i< distance.length;i++){
            System.out.println("质心计算" + distance[i]);}
    }

    /**
     * 标准欧式距离计算
     * @param vector1
     * @param vector2
     */
    public static void standerd_distance(double[] vector1, double[][] vector2) {
        double[] s = new double[vector2.length + 1];
        double[] avg = new double[vector2.length];
        // vector2均值
        for (int i = 0; i < vector2.length; i++) {
            for (int j = 0; j < vector2[0].length; j++) {
                avg[i] += vector2[i][j];
            }
        }
        // vector1均值
        double avg0 = 0;
        for (int i = 0; i < vector1.length; i++) {
            avg0 += vector1[i];
        }
        //vector1方差
        if (vector1.length == vector2[0].length) {
            for (int i = 0; i < vector1.length; i++) {
                s[0] += Math.pow(vector1[i] - avg0, 2);
            }
            s[0] = Math.sqrt(s[0] / vector2.length);
        }
        //vector2方差
        for (int i = 0; i < vector2.length; i++) {
            for (int j = 0; j < vector2[0].length; j++) {
                s[i + 1] += Math.pow(vector2[i][j] - avg[i], 2);
            }
            s[i + 1] = Math.sqrt(s[i] / vector2.length);
        }

        //标准化欧氏距离
        double distance[] = new double[vector2.length];
        for (int i = 0; i < vector1.length; i++) {
            for (int j = 0; j < vector2.length; j++) {
                double temp = Math.pow((vector1[i] - vector2[j][i]), 2) / s[i];
                distance[j] = distance[j] + temp;
            }
        }
        for (int i = 0; i < distance.length; i++) {
            distance[i] = Math.sqrt(distance[i]);
        }
        for (int i = 0; i < distance.length; i++) {
            System.out.println(distance[i]);
        }
}
    /**
     * getface2 通过欧氏距离计算提高性能
     * @param image
     * @return
     */
    public static Mat getFace2(Mat image) {
        // 2  特征匹配类
        MatOfRect face = new MatOfRect();
        // 3 特征匹配
        faceDetector.detectMultiScale(image, face);
        Rect[] rects = face.toArray();
//        System.out.println("检测到 "+rects.length+" 个人脸");
        //如果检测到了人脸
        if (rects != null && rects.length >= 1) {
            for (int i = 0; i < rects.length; i++) {
                Point point = new Point(rects[i].x, rects[i].y);
                Point point1 = new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height);
                Imgproc.rectangle(image, point, point1, new Scalar(0, 255, 0));
            }
        }
        /**
         * 做状态调整，如果检测人脸数为0，那么count计数则为0
         */
        if (rects == null || rects.length == 0){
            count = 0;
            counts = count;
        }
        /**
        * 人脸检测，第一次，如果count大于等于2，说明已经检测过了，不再检测
        */
        if (rects != null && rects.length == 1 && count < 1){
            System.out.println("人脸检测====1");
            FaceImageContrast.contrast(image);
            count +=1;
        }
        /**
         * 多张人脸检测，且counts小于1，只检测一次
         */
        if (rects != null && rects.length > 1 && counts < 1){
            for (int i = 0; i < rects.length; i++) {
                FaceImageContrast.contrast(image);
                System.out.println(" 检测到多张人脸  ");
                counts +=1;
            }
        }
        /**
         * 多张人脸检测，count大于等于1的时候，进行质心计算，不再重复检测
         */
        if (rects != null && rects.length > 1 && counts >= 1) {
            int k = 0;
            currentPointList = new double[rects.length][];
            for (int i = 0; i < rects.length; i++) {
                double[] single = {rects[i].x, rects[i].y};
                    currentPointList[i] = single;
            }
            //将上面装填好的坐标数组，挨个取出来和上一次保持的数组内的其它数组做欧氏距离计算
            for (int j = 0; j < currentPointList.length; j++) {
                double [] singleTemp = currentPointList[j];
                standerd_distance(singleTemp,lastPointList);
                //TODO 如果计算后的欧式距离没有匹配的人脸数据，那么进行人脸检测 if(){FaceImageContrast.contrast(image);}
            }
            //更新当前人脸point到上一次数组中
            lastPointList = currentPointList;
        }
        return image;
    }
    /**
     * OpenCV-4.5.3 图片人脸检测，并将检测到的人脸调用裁切方法裁切后保存到硬盘
     * @return: void
     * @date: 2019年5月7日12:16:55
     */
    public static void findFace() {
        // 1 读取OpenCV自带的人脸识别特征XML文件
        //OpenCV 图像识别库一般位于 opencv\sources\data 下面
//        CascadeClassifier facebook=new CascadeClassifier("D:\\Sofeware\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
        // 2 读取测试图片
        String imgPath = "C:\\Users\\zouqi\\Pictures\\face1.png";
        Mat image=Imgcodecs.imread(imgPath);
        if(image.empty()){
            System.out.println("image 内容不存在！");
            return;
        }
        // 3 特征匹配
        MatOfRect face = new MatOfRect();
        faceDetector.detectMultiScale(image, face);
        // 4 匹配 Rect 矩阵 数组
        Rect[] rects=face.toArray();
        System.out.println("匹配到 "+rects.length+" 个人脸");
        // 5 为每张识别到的人脸画一个圈
        int i =1 ;
        for (Rect rect : face.toArray()) {
            Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height),
                    new Scalar(0, 255, 0), 3);
            imageCut(imgPath, ConnectionLocal.STORE_FILE_PATH+i+".jpg", rect.x, rect.y, rect.width, rect.height);// 进行图片裁剪
            i++;
        }
        // 6 展示图片
        HighGui.imshow("人脸识别", image);
        HighGui.waitKey(0);
    }

    /**
     * 裁剪人脸
     * @param imagePath
     * @param outFile
     * @param posX
     * @param posY
     * @param width
     * @param height
     */
    public static void imageCut(String imagePath, String outFile, int posX, int posY, int width, int height) {
        // 原始图像
        Mat image = Imgcodecs.imread(imagePath);
        // 截取的区域：参数,坐标X,坐标Y,截图宽度,截图长度
        Rect rect = new Rect(posX, posY, width, height);
        // 两句效果一样
        Mat sub = image.submat(rect); // Mat sub = new Mat(image,rect);
        Mat mat = new Mat();
        Size size = new Size(width, height);
        Imgproc.resize(sub, mat, size);// 将人脸进行截图并保存
        Imgcodecs.imwrite(outFile, mat);
        System.out.println(String.format("图片裁切成功，裁切后图片文件为： %s", outFile));

    }

    /**
     * 人脸比对 1对1
     * @param img_1
     * @param img_2
     * @return
     */
    public static double compare_image(String img_1, String img_2) {
        Mat mat_1 = conv_Mat(img_1);
        Mat mat_2 = conv_Mat(img_2);
        Mat hist_1 = new Mat();
        Mat hist_2 = new Mat();

        //颜色范围
        MatOfFloat ranges = new MatOfFloat(0f, 256f);
        //直方图大小， 越大匹配越精确 (越慢)
        MatOfInt histSize = new MatOfInt(2000);

        Imgproc.calcHist(Arrays.asList(mat_1), new MatOfInt(0), new Mat(), hist_1, histSize, ranges);
        Imgproc.calcHist(Arrays.asList(mat_2), new MatOfInt(0), new Mat(), hist_2, histSize, ranges);

        // CORREL 相关系数
        double res = Imgproc.compareHist(hist_1, hist_2, Imgproc.CV_COMP_CORREL);
        return res;
    }

    public static double compare_image(Mat img_1, Mat img_2) {
        //这里由于是接收从视频截取的MAT类型文件作为入参，因此需要再次调用该类库类型的方法做灰度化处理
        Mat mat_1 = conv_Mat(img_1);
        Mat mat_2 = img_2;
        Mat hist_1 = new Mat();
        Mat hist_2 = new Mat();

        //颜色范围
        MatOfFloat ranges = new MatOfFloat(0f, 256f);
        //直方图大小， 越大匹配越精确 (越慢)
        MatOfInt histSize = new MatOfInt(1000);
        if (mat_2 == null || mat_1 == null){
            return 0D;
        }else {
            /**坑~~~~
             * mat_1 的值是null 而不是mat_1是null，所以不会报错，会在asList方法里进行处理并将值是Null的对象返回为一个null
             *         ArrayList(E[] array) {
             *             a = Objects.requireNonNull(array);
             *         }
             * 所以在Arrays.asList(mat_1)的时候如果mat_1是null，不会报错，因为mat_1是个对象，只是它的值是null
             */
            Imgproc.calcHist(Arrays.asList(mat_1), new MatOfInt(0), new Mat(), hist_1, histSize, ranges);
            Imgproc.calcHist(Arrays.asList(mat_2), new MatOfInt(0), new Mat(), hist_2, histSize, ranges);
        }
        // CORREL 相关系数
        double res = Imgproc.compareHist(hist_1, hist_2, Imgproc.CV_COMP_CORREL);
        return res;
    }

    /**
     * 灰度化人脸
     * @param img
     * @return
     */
    public static Mat conv_Mat(String img) {
        Mat image0 = Imgcodecs.imread(img);

        Mat image1 = new Mat();
        // 灰度化
        Imgproc.cvtColor(image0, image1, Imgproc.COLOR_BGR2GRAY);
        // 探测人脸
        MatOfRect faceDetections = new MatOfRect();
        faceDetector.detectMultiScale(image1, faceDetections);
        // rect中人脸图片的范围
        for (Rect rect : faceDetections.toArray()) {
            Mat face = new Mat(image1, rect);
            return face;
        }
        return null;
    }
    public static Mat conv_Mat(Mat mat) {
        Mat image0 = mat;

        Mat image1 = new Mat();
        // 灰度化
        Imgproc.cvtColor(image0, image1, Imgproc.COLOR_BGR2GRAY);
        // 探测人脸
        MatOfRect faceDetections = new MatOfRect();
        faceDetector.detectMultiScale(image1, faceDetections);
        // rect中人脸图片的范围
        for (Rect rect : faceDetections.toArray()) {
            Mat face = new Mat(image1, rect);
            return face;
        }
        return null;
    }
    /**
     * OpenCV-4.5.3 将摄像头拍摄的视频写入本地
     * @return: void
     * @date: 2019年8月19日 17:20:48
     */
    public static void writeVideo() {
        //1 如果要从摄像头获取视频 则要在 VideoCapture 的构造方法写 0
        VideoCapture capture=new VideoCapture(0);
        Mat video=new Mat();
        int index=0;
        Size size=new Size(capture.get(Videoio.CAP_PROP_FRAME_WIDTH),capture.get(Videoio.CAP_PROP_FRAME_HEIGHT));
        VideoWriter writer=new VideoWriter("D:/a.mp4",VideoWriter.fourcc('D', 'I', 'V', 'X'), 25.0,size, true);
        while(capture.isOpened()) {
            capture.read(video);//2 将摄像头的视频写入 Mat video 中
            writer.write(video);
            HighGui.imshow("像头获取视频", video);//3 显示图像
            index=HighGui.waitKey(100);//4 获取键盘输入
            if(index==27) {//5 如果是 Esc 则退出
                capture.release();
                writer.release();
                return;
            }
        }
    }

}


